// Copyright 2014 the V8 project authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

#include "src/arguments-inl.h"
#include "src/counters.h"
#include "src/elements.h"
#include "src/heap/factory.h"
#include "src/heap/heap-inl.h"
#include "src/message-template.h"
#include "src/objects-inl.h"
#include "src/objects/js-array-buffer-inl.h"
#include "src/runtime/runtime-utils.h"
#include "src/runtime/runtime.h"

namespace v8 {
namespace internal {

    RUNTIME_FUNCTION(Runtime_ArrayBufferDetach)
    {
        HandleScope scope(isolate);
        DCHECK_EQ(1, args.length());
        Handle<Object> argument = args.at(0);
        // This runtime function is exposed in ClusterFuzz and as such has to
        // support arbitrary arguments.
        if (!argument->IsJSArrayBuffer()) {
            THROW_NEW_ERROR_RETURN_FAILURE(
                isolate, NewTypeError(MessageTemplate::kNotTypedArray));
        }
        Handle<JSArrayBuffer> array_buffer = Handle<JSArrayBuffer>::cast(argument);
        if (!array_buffer->is_detachable()) {
            return ReadOnlyRoots(isolate).undefined_value();
        }
        if (array_buffer->backing_store() == nullptr) {
            CHECK_EQ(0, array_buffer->byte_length());
            return ReadOnlyRoots(isolate).undefined_value();
        }
        // Shared array buffers should never be detached.
        CHECK(!array_buffer->is_shared());
        DCHECK(!array_buffer->is_external());
        void* backing_store = array_buffer->backing_store();
        size_t byte_length = array_buffer->byte_length();
        array_buffer->set_is_external(true);
        isolate->heap()->UnregisterArrayBuffer(*array_buffer);
        array_buffer->Detach();
        isolate->array_buffer_allocator()->Free(backing_store, byte_length);
        return ReadOnlyRoots(isolate).undefined_value();
    }

    RUNTIME_FUNCTION(Runtime_TypedArrayCopyElements)
    {
        HandleScope scope(isolate);
        DCHECK_EQ(3, args.length());
        CONVERT_ARG_HANDLE_CHECKED(JSTypedArray, target, 0);
        CONVERT_ARG_HANDLE_CHECKED(Object, source, 1);
        CONVERT_NUMBER_ARG_HANDLE_CHECKED(length_obj, 2);

        size_t length;
        CHECK(TryNumberToSize(*length_obj, &length));

        ElementsAccessor* accessor = target->GetElementsAccessor();
        return accessor->CopyElements(source, target, length);
    }

    RUNTIME_FUNCTION(Runtime_TypedArrayGetLength)
    {
        HandleScope scope(isolate);
        DCHECK_EQ(1, args.length());
        CONVERT_ARG_HANDLE_CHECKED(JSTypedArray, holder, 0);
        return holder->length();
    }

    RUNTIME_FUNCTION(Runtime_ArrayBufferViewWasDetached)
    {
        HandleScope scope(isolate);
        DCHECK_EQ(1, args.length());
        return isolate->heap()->ToBoolean(JSTypedArray::cast(args[0])->WasDetached());
    }

    RUNTIME_FUNCTION(Runtime_TypedArrayGetBuffer)
    {
        HandleScope scope(isolate);
        DCHECK_EQ(1, args.length());
        CONVERT_ARG_HANDLE_CHECKED(JSTypedArray, holder, 0);
        return *holder->GetBuffer();
    }

    namespace {

        template <typename T>
        bool CompareNum(T x, T y)
        {
            if (x < y) {
                return true;
            } else if (x > y) {
                return false;
            } else if (!std::is_integral<T>::value) {
                double _x = x, _y = y;
                if (x == 0 && x == y) {
                    /* -0.0 is less than +0.0 */
                    return /*std::*/signbit(_x) && !/*std::*/signbit(_y);
                } else if (!/*std::*/isnan(_x) && /*std::*/isnan(_y)) {
                    /* number is less than NaN */
                    return true;
                }
            }
            return false;
        }

    } // namespace

    RUNTIME_FUNCTION(Runtime_TypedArraySortFast)
    {
        HandleScope scope(isolate);
        DCHECK_EQ(1, args.length());

        CONVERT_ARG_HANDLE_CHECKED(Object, target_obj, 0);

        Handle<JSTypedArray> array;
        const char* method = "%TypedArray%.prototype.sort";
        ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
            isolate, array, JSTypedArray::Validate(isolate, target_obj, method));

        // This line can be removed when JSTypedArray::Validate throws
        // if array.[[ViewedArrayBuffer]] is detached(v8:4648)
        if (V8_UNLIKELY(array->WasDetached()))
            return *array;

        size_t length = array->length_value();
        if (length <= 1)
            return *array;

        Handle<FixedTypedArrayBase> elements(
            FixedTypedArrayBase::cast(array->elements()), isolate);
        switch (array->type()) {
#define TYPED_ARRAY_SORT(Type, type, TYPE, ctype)                                                                 \
    case kExternal##Type##Array: {                                                                                \
        ctype* data = static_cast<ctype*>(elements->DataPtr());                                                   \
        if (kExternal##Type##Array == kExternalFloat64Array || kExternal##Type##Array == kExternalFloat32Array) { \
            if (COMPRESS_POINTERS_BOOL && alignof(ctype) > kTaggedSize) {                                         \
                /* TODO(ishell, v8:8875): See UnalignedSlot<T> for details. */                                    \
                std::sort(UnalignedSlot<ctype>(data),                                                             \
                    UnalignedSlot<ctype>(data + length), CompareNum<ctype>);                                      \
            } else {                                                                                              \
                std::sort(data, data + length, CompareNum<ctype>);                                                \
            }                                                                                                     \
        } else {                                                                                                  \
            if (COMPRESS_POINTERS_BOOL && alignof(ctype) > kTaggedSize) {                                         \
                /* TODO(ishell, v8:8875): See UnalignedSlot<T> for details. */                                    \
                std::sort(UnalignedSlot<ctype>(data),                                                             \
                    UnalignedSlot<ctype>(data + length));                                                         \
            } else {                                                                                              \
                std::sort(data, data + length);                                                                   \
            }                                                                                                     \
        }                                                                                                         \
        break;                                                                                                    \
    }

            TYPED_ARRAYS(TYPED_ARRAY_SORT)
#undef TYPED_ARRAY_SORT
        }

        return *array;
    }

    RUNTIME_FUNCTION(Runtime_IsTypedArray)
    {
        HandleScope scope(isolate);
        DCHECK_EQ(1, args.length());
        return isolate->heap()->ToBoolean(args[0]->IsJSTypedArray());
    }

    // 22.2.3.23 %TypedArray%.prototype.set ( overloaded [ , offset ] )
    RUNTIME_FUNCTION(Runtime_TypedArraySet)
    {
        HandleScope scope(isolate);
        Handle<JSTypedArray> target = args.at<JSTypedArray>(0);
        Handle<Object> obj = args.at(1);
        Handle<Smi> offset = args.at<Smi>(2);

        DCHECK(!target->WasDetached()); // Checked in TypedArrayPrototypeSet.
        DCHECK(!obj->IsJSTypedArray()); // Should be handled by CSA.
        DCHECK_LE(0, offset->value());

        const uint32_t uint_offset = static_cast<uint32_t>(offset->value());

        if (obj->IsNumber()) {
            // For number as a first argument, throw TypeError
            // instead of silently ignoring the call, so that
            // users know they did something wrong.
            // (Consistent with Firefox and Blink/WebKit)
            THROW_NEW_ERROR_RETURN_FAILURE(
                isolate, NewTypeError(MessageTemplate::kInvalidArgument));
        }

        ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, obj,
            Object::ToObject(isolate, obj));

        Handle<Object> len;
        ASSIGN_RETURN_FAILURE_ON_EXCEPTION(
            isolate, len,
            Object::GetProperty(isolate, obj, isolate->factory()->length_string()));
        ASSIGN_RETURN_FAILURE_ON_EXCEPTION(isolate, len,
            Object::ToLength(isolate, len));

        if (uint_offset + len->Number() > target->length_value()) {
            THROW_NEW_ERROR_RETURN_FAILURE(
                isolate, NewRangeError(MessageTemplate::kTypedArraySetSourceTooLarge));
        }

        uint32_t int_l;
        CHECK(DoubleToUint32IfEqualToSelf(len->Number(), &int_l));

        Handle<JSReceiver> source = Handle<JSReceiver>::cast(obj);
        ElementsAccessor* accessor = target->GetElementsAccessor();
        return accessor->CopyElements(source, target, int_l, uint_offset);
    }

} // namespace internal
} // namespace v8
